<?php

abstract class AbstractMapper
{

    protected $_db;
    protected $_tableName;
    protected $_model;
    protected $_tableColumns;

    public function __construct()
    {
	$this->_init();
    }

    abstract protected function _init();

    public function getObjectList($params)
    {
	if (!$this->getModel()) {
	    return $params;
	}
	if (empty($params)) {
	    return array();
	}
	$className = get_class($this->getModel());
	if (!class_exists($className)) {
	    return $params;
	}

	$objectList = array();
	foreach ($params as $param) {
	    $objectList[] = new $className($param);
	}
	return $objectList;
    }

    public function getTableColumns()
    {
	if ($this->_tableColumns === null) {
	    if ($this->getTableName()) {
		$this->_tableColumns = $this->getDb()->getSchema()->getTable($this->getTableName())->columnNames;
	    }
	}
	return $this->_tableColumns;
    }

    public function getDb()
    {
	if ($this->_db === null) {
	    $this->_db = Yii::app()->db;
	}
	return $this->_db;
    }

    public function createSqlCommand($params = null)
    {
	$db = $this->getDb();
	if (!$db) {
	    throw new Exception("Db is not defined");
	}

	return $db->createCommand($params);
    }

    public function getTableName()
    {
	return $this->_tableName;
    }

    public function setTableName($tableName)
    {
	$this->_tableName = $tableName;
    }

    public function getModel()
    {
	return $this->_model;
    }

    public function setModel(AbstractModel $model)
    {
	$this->_model = $model;
    }

    public function createEmptyModel()
    {
	$className = get_class($this->getModel());
	return new $className;
    }

    public function getById($id)
    {
	$tableName = $this->getTableName();
	$model = $this->getModel();
	if (!$id || !$tableName || !$model) {
	    return null;
	}
	$data = $this->createSqlCommand()
		->select("*")
		->from($tableName)
		->where("id = :id", array(":id" => intval($id)))
		->queryRow();

	if (empty($data)) {
	    return null;
	}

	$className = get_class($model);
	if (!class_exists($className)) {
	    throw new Exception("Class not exists");
	}
	$object = new $className;
	$object->setDbData((object) $data);
	return $object;
    }

    public function getBy($conditions, $params = array(), $sqlPart = null)
    {
	if (empty($conditions)) {
	    return null;
	}

	$className = get_class($this->getModel());
	if (!class_exists($className)) {
	    throw new Exception("Class not exists");
	}

	$sql = $this->createSqlCommand($sqlPart);
	if (!isset($sqlPart['select'])) {
	    $sql->select('*');
	}
	$sql->from($this->getTableName())
		->where($conditions, $params);
	$data = $sql->queryRow();
	if (empty($data)) {
	    return null;
	}

	$object = new $className;
	$object->setDbData((object) $data);
	return $object;
    }

    public function getAllBy($conditions, $params = array(), $sqlPart = null)
    {
	if (empty($conditions)) {
	    return null;
	}

	$className = get_class($this->getModel());
	if (!class_exists($className)) {
	    throw new Exception("Class not exists");
	}
	$sql = $this->createSqlCommand($sqlPart);
	if (!isset($sqlPart['select'])) {
	    $sql->select('*');
	}
	$sql->from($this->getTableName())
		->where($conditions, $params);
	return $this->getObjectList($sql->queryAll());
    }

    public function getAll($sql = array())
    {
	$sql = $this->createSqlCommand($sql)
		->select('*')
		->from($this->getTableName());
	return $this->getObjectList($sql->queryAll());
    }

    public function getAllByQuery($sql)
    {
	$sql = $this->createSqlCommand($sql);
	return $this->getObjectList($sql->queryAll());
    }

    public function getCount($sql)
    {
	return $this->createSqlCommand($sql)
		->select("COUNT(id)")
		->from($this->getTableName())
		->queryScalar();
    }

    public function insert(array $data)
    {
	$table = $this->getTableName();
	if (!$table || empty($data)) {
	    return false;
	}
	if ($this->createSqlCommand()->insert($table, $data)) {
	    return $this->createSqlCommand()->getConnection()->getLastInsertID();
	}

	return false;
    }

    public function update(array $data, $conditions = '', $params = array())
    {
	$table = $this->getTableName();
	if (!$table || empty($data)) {
	    return false;
	}

	return $this->createSqlCommand()->update($table, $data, $conditions, $params);
    }

    public function delete($conditions = '', $params = array())
    {
	$table = $this->getTableName();
	if (!$table) {
	    return false;
	}

	return $this->createSqlCommand()->delete($table, $conditions, $params);
    }

    /**
     *
     * @param array $firstData
     * @param array $secondData
     * @param type $tableName
     */
    public function makeLink(array $firstData, array $secondData, $tableName = null)
    {
	if (!$tableName) {
	    $tableName = reset(array_keys($firstData)) . '_link_' . reset(array_keys($secondData));
	}

	$data = array(
	    reset(array_keys($firstData)) . '_id' => reset(array_values($firstData)),
	    reset(array_keys($secondData)) . '_id' => reset(array_values($secondData))
	);
	$conditions = array();
	$params = array();
	foreach ($data as $key => $value) {
	    $conditions[] = $key . '=:' . $key;
	    $params[':' . $key] = $value;
	}
	$this->createSqlCommand()->delete($tableName, implode(" AND ", $conditions), $params);

	return $this->createSqlCommand()->insert($tableName, $data);
    }

    public function getAllFeatures($pk, $parentTable, $childTable, $sqlPart = array())
    {
	if (!$pk) {
	    return false;
	}
	$sql = $this->createSqlCommand($sqlPart)
		->select(array($childTable . '.*'))
		->from($childTable)
		->join($parentTable . '_link_' . $childTable, $parentTable . '_link_' . $childTable . '.' . $childTable . '_id = ' . $childTable . '.id')
		->where($parentTable . '_link_' . $childTable . '.' . $parentTable . '_id = :param', array(':param' => $pk));
	return $this->getObjectList($sql->queryAll());
    }

    public function compareItems($tableName, array $items)
    {
	if (!$tableName || empty($items)) {
	    return false;
	}

	return $this->createSqlCommand()->insert($tableName, $items);
    }

    public function getAllWithRelation($sql, $with)
    {
	if (!$sql || !$with) {
	    return false;
	}

	$relationSchema = array();
	foreach ($with as $key => $service) {
	    $relationSchema[$service] = array(
		'tableKey' => 't' . $key,
		'table' => FactoryService::get($service)->getMapper()->getTableName(),
		'colums' => FactoryService::get($service)->getMapper()->getTableColumns()
	    );
	}
	foreach ($relationSchema as $k => $item) {
	    foreach ($item['colums'] as $key => $column) {
		$columnKey = $item['table'] . "." . $column . " " . $item['tableKey'] . "_c" . $key . "";
		$sql['select'][] = $columnKey;
		$relationSchema[$k]['fields'][$item['tableKey'] . "_c" . $key] = $column;
	    }
	}		
	return $this->getRelationList($this->createSqlCommand($sql)->queryAll(), $relationSchema);
    }

    public function getRelationList($result, $relationSchema)
    {
	if (empty($result) || empty($relationSchema)) {
	    return false;
	}

	$models = array();
	foreach ($result as $row) {
	    $rowFields = $row;
	    $model = $this->createEmptyModel();
	    foreach ($relationSchema as $service => $item) {
		$serviceKeys = array_intersect_key($rowFields, $item['fields']);
		if (array_filter($serviceKeys)) {
		    $serviceModel = FactoryService::get($service)->createEmptyModel();
		    foreach ($serviceKeys as $key => $value) {
			$serviceModel->$item['fields'][$key] = $value;
		    }
		    $model->$service = $serviceModel;
		}
		$rowFields = array_diff_key($rowFields, $item['fields']);
	    }
	    $model->setDbData((object) $rowFields);
	    $models[] = $model;
	}
	return $models;
    }

}